use alloc::{string::String, sync::Arc, vec::Vec};
use context::SysContext;
use data::Stat;
use x86_64::{
    registers::{
        control::{Efer, EferFlags},
        model_specific::{LStar, SFMask, Star},
        rflags::RFlags,
    },
    VirtAddr,
};

use crate::{
    arch::{
        apic::LAPIC,
        gdt::Selectors,
        syscall::{
            SYS_CLOSE, SYS_EXECVE, SYS_EXIT, SYS_FSTAT, SYS_GETPID, SYS_GETTID, SYS_MMAP, SYS_OPEN,
            SYS_READ, SYS_SCHED_YIELD, SYS_WAIT4, SYS_WRITE,
        },
    },
    memory::copy_from_user,
    process::{get_current_thread, process::WeakSharedProcess, thread::WeakSharedThread},
};

pub type SysResult = Result<usize, SysError>;

#[repr(isize)]
#[derive(Debug)]
pub enum SysError {
    EUNDEF = 0,
    EPERM = 1,
    ENOENT = 2,
    ESRCH = 3,
    EINTR = 4,
    EIO = 5,
    ENXIO = 6,
    E2BIG = 7,
    ENOEXEC = 8,
    EBADF = 9,
    ECHILD = 10,
    EAGAIN = 11,
    ENOMEM = 12,
    EACCES = 13,
    EFAULT = 14,
    ENOTBLK = 15,
    EBUSY = 16,
    EEXIST = 17,
    EXDEV = 18,
    ENODEV = 19,
    ENOTDIR = 20,
    EISDIR = 21,
    EINVAL = 22,
    ENFILE = 23,
    EMFILE = 24,
    ENOTTY = 25,
    ETXTBSY = 26,
    EFBIG = 27,
    ENOSPC = 28,
    ESPIPE = 29,
    EROFS = 30,
    EMLINK = 31,
    EPIPE = 32,
    EDOM = 33,
    ERANGE = 34,
    EDEADLK = 35,
    ENAMETOOLONG = 36,
    ENOLCK = 37,
    ENOSYS = 38,
    ENOTEMPTY = 39,
    ELOOP = 40,
    EIDRM = 43,
    ENOTSOCK = 80,
    ENOPROTOOPT = 92,
    EPFNOSUPPORT = 96,
    EAFNOSUPPORT = 97,
    ENOBUFS = 105,
    EISCONN = 106,
    ENOTCONN = 107,
    ETIMEDOUT = 110,
    ECONNREFUSED = 111,
}

#[allow(non_snake_case)]
impl core::fmt::Display for SysError {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        use self::SysError::*;
        write!(
            f,
            "{}",
            match self {
                EPERM => "Operation not permitted",
                ENOENT => "No such file or directory",
                ESRCH => "No such process",
                EINTR => "Interrupted system call",
                EIO => "I/O error",
                ENXIO => "No such device or address",
                E2BIG => "Argument list too long",
                ENOEXEC => "Exec format error",
                EBADF => "Bad file number",
                ECHILD => "No child processes",
                EAGAIN => "Try again",
                ENOMEM => "Out of memory",
                EACCES => "Permission denied",
                EFAULT => "Bad address",
                ENOTBLK => "Block device required",
                EBUSY => "Device or resource busy",
                EEXIST => "File exists",
                EXDEV => "Cross-device link",
                ENODEV => "No such device",
                ENOTDIR => "Not a directory",
                EISDIR => "Is a directory",
                EINVAL => "Invalid argument",
                ENFILE => "File table overflow",
                EMFILE => "Too many open files",
                ENOTTY => "Not a typewriter",
                ETXTBSY => "Text file busy",
                EFBIG => "File too large",
                ENOSPC => "No space left on device",
                ESPIPE => "Illegal seek",
                EROFS => "Read-only file system",
                EMLINK => "Too many links",
                EPIPE => "Broken pipe",
                EDOM => "Math argument out of domain of func",
                ERANGE => "Math result not representable",
                EDEADLK => "Resource deadlock would occur",
                ENAMETOOLONG => "File name too long",
                ENOLCK => "No record locks available",
                ENOSYS => "Function not implemented",
                ENOTEMPTY => "Directory not empty",
                ELOOP => "Too many symbolic links encountered",
                ENOTSOCK => "Socket operation on non-socket",
                ENOPROTOOPT => "Protocol not available",
                EPFNOSUPPORT => "Protocol family not supported",
                EAFNOSUPPORT => "Address family not supported by protocol",
                ENOBUFS => "No buffer space available",
                EISCONN => "Transport endpoint is already connected",
                ENOTCONN => "Transport endpoint is not connected",
                ECONNREFUSED => "Connection refused",
                _ => "Unknown error",
            },
        )
    }
}

pub fn handle_syscall(thread: &WeakSharedThread, context: &mut SysContext) -> bool {
    let num = context.get_syscall_num();
    let args = context.get_syscall_args();

    let mut syscall = Syscall {
        thread,
        context,
        exit: false,
    };

    let ret = syscall.syscall(num, args);
    let exit = syscall.exit;
    context.set_syscall_ret(ret as usize);
    exit
}

pub mod context;
pub mod data;

mod fs;
mod mm;
mod proc;

struct Syscall<'a> {
    pub thread: &'a WeakSharedThread,
    pub context: &'a mut SysContext,
    pub exit: bool,
}

impl Syscall<'_> {
    pub fn process(&self) -> WeakSharedProcess {
        self.thread.upgrade().unwrap().read().process.clone()
    }

    #[allow(unused_variables)]
    fn syscall(&mut self, id: usize, args: (usize, usize, usize, usize, usize, usize)) -> isize {
        let begin_time = unsafe { core::arch::x86_64::_rdtsc() };
        let cid = unsafe { LAPIC.lock().id() };

        let pid = self.process().upgrade().unwrap().read().id.0 as usize;
        let tid = self.thread.upgrade().unwrap().read().id.0 as usize;

        let ret = match id {
            SYS_MMAP => self.sys_mmap(args.0, args.1, args.2, args.3),

            SYS_SCHED_YIELD => self.sys_yield(),
            SYS_GETPID => self.sys_getpid(),
            SYS_GETTID => self.sys_gettid(),
            SYS_EXECVE => self.sys_exec(
                args.0 as *const u8,
                args.1 as *const *const u8,
                args.2 as *const *const u8,
            ),
            SYS_EXIT => self.sys_exit(args.0),
            SYS_WAIT4 => self.sys_waitpid(args.0),

            SYS_OPEN => self.sys_open(args.0 as *const u8, args.1),
            SYS_CLOSE => self.sys_close(args.0),
            SYS_READ => self.sys_read(args.0, args.1, args.2),
            SYS_WRITE => self.sys_write(args.0, args.1, args.2),
            SYS_FSTAT => self.sys_fstat(args.0, args.1 as *mut Stat),
            _ => Err(SysError::ENOSYS),
        };

        match ret {
            Ok(code) => code as isize,
            Err(err) => -(err as isize),
        }
    }
}

pub fn check_and_clone_cstr(user: *const u8) -> Result<String, SysError> {
    if user.is_null() {
        Ok(String::new())
    } else {
        let mut buffer = Vec::new();
        for i in 0.. {
            let addr = unsafe { user.add(i) };
            let data = copy_from_user(addr).ok_or(SysError::EFAULT)?;
            if data == 0 {
                break;
            }
            buffer.push(data);
        }
        String::from_utf8(buffer).map_err(|_| SysError::EFAULT)
    }
}

pub fn check_and_clone_cstr_array(user: *const *const u8) -> Result<Vec<String>, SysError> {
    if user.is_null() {
        Ok(Vec::new())
    } else {
        let mut buffer = Vec::new();
        for i in 0.. {
            let addr = unsafe { user.add(i) };
            let str_ptr = copy_from_user(addr).ok_or(SysError::EFAULT)?;
            if str_ptr.is_null() {
                break;
            }
            let string = check_and_clone_cstr(str_ptr)?;
            buffer.push(string);
        }
        Ok(buffer)
    }
}

pub fn init() {
    let handler_addr = syscall_handler as *const () as u64;

    SFMask::write(RFlags::INTERRUPT_FLAG);
    LStar::write(VirtAddr::new(handler_addr as u64));

    let (code_selector, data_selector) = Selectors::get_kernel_segments();
    let (user_code_selector, user_data_selector) = Selectors::get_user_segments();

    Star::write(
        user_code_selector,
        user_data_selector,
        code_selector,
        data_selector,
    )
    .unwrap();

    unsafe {
        Efer::write(Efer::read() | EferFlags::SYSTEM_CALL_EXTENSIONS);
    }
}

pub fn do_syscall_handler(address: *mut SysContext) {
    let context = &mut unsafe { *address };
    let current_thread = get_current_thread();
    if handle_syscall(&Arc::downgrade(&current_thread), context) {
        loop {
            x86_64::instructions::interrupts::enable_and_hlt();
            crate::process::r#yield();
        }
    }
}

#[naked]
extern "C" fn syscall_handler() {
    unsafe {
        core::arch::naked_asm!(
            "cli",
            crate::push_sys_context!(),
            "mov rdi, rsp",
            "call {syscall_matcher}",
            crate::pop_sys_context!(),
            "sti",
            "sysret",
            syscall_matcher = sym do_syscall_handler,
        );
    }
}
